﻿using AIStudio.ConSole.Redis.Core;
using CSRedis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace AIStudio.ConSole.Redis.Ch04
{
    class TestCh04
    {
        /********************************************************************************************************
        # <start id="_1313_14472_8342"/>
        def list_item(conn, itemid, sellerid, price):
        inventory = "inventory:%s"%sellerid
        item = "%s.%s" % (itemid, sellerid)
        end = time.time() + 5
        pipe = conn.pipeline()

        while time.time() < end:
            try:
                pipe.watch(inventory)                    #A
                if not pipe.sismember(inventory, itemid):#B
                    pipe.unwatch()                       #E
                    return None

                pipe.multi()                             #C
                pipe.zadd("market:", {item: price
        })      #C
                pipe.srem(inventory, itemid)             #C
                pipe.execute()                           #F
                return True
            except redis.exceptions.WatchError:          #D
                pass                                     #D
        return False
        # <end id="_1313_14472_8342"/>
        *********************************************************************************************************/

        #region list_item
        public bool list_item(CSRedisClient conn, string itemid, string sellerid, decimal price)
        {
            var inventory = $"inventory:{sellerid}";
            var item = $"{itemid}.{sellerid}";
            var end = DateTime.Now.AddSeconds(5);
            var pipe = conn.StartPipe();
            while (DateTime.Now < end)
            {
                try
                {
                    if (conn.SIsMember(inventory, itemid) != true)
                        continue;

                    pipe.ZAdd("market:", (price, item));
                    pipe.SRem(inventory, itemid);
                    pipe.EndPipe();
                    return true;
                }
                catch (Exception)
                {
                    continue;
                }
            }

            return false;
        }
        #endregion

        /********************************************************************************************************
        # <start id="_1313_14472_8353"/>
        def purchase_item(conn, buyerid, itemid, sellerid, lprice):
        buyer = "users:%s"%buyerid
        seller = "users:%s" % sellerid
        item = "%s.%s"%(itemid, sellerid)
        inventory = "inventory:%s" % buyerid
        end = time.time() + 10
        pipe = conn.pipeline()

        while time.time() < end:
        try:
        pipe.watch("market:", buyer)                #A

        price = pipe.zscore("market:", item)        #B
        funds = int (pipe.hget(buyer, "funds"))      #B
        if price != lprice or price > funds:        #B
            pipe.unwatch()                          #B
            return None

        pipe.multi()                                #C
        pipe.hincrby(seller, "funds", int (price))   #C
        pipe.hincrby(buyer, "funds", int (-price))   #C
        pipe.sadd(inventory, itemid)                #C
        pipe.zrem("market:", item)                  #C
        pipe.execute()                              #C
        return True
        except redis.exceptions.WatchError:             #D
        pass                                        #D

        return False
        # <end id="_1313_14472_8353"/>
        *********************************************************************************************************/

        public bool purchase_item(CSRedisClient conn, string buyerid, string itemid, string sellerid, decimal lprice)
        {
            var buyer = $"users:{buyerid}";
            var seller = $"users:{sellerid}";
            var item = $"{itemid}.{sellerid}";
            var inventory = $"inventory:{sellerid}";

            var end = DateTime.Now.AddSeconds(10);

            var pipe = conn.StartPipe();
            while (DateTime.Now < end)
            {
                try
                {
                    var price = conn.ZScore("market:", item);
                    var funds = conn.HGet(buyer, "funds");
                    if (price != lprice || price > decimal.Parse(funds))
                    {
                        return false;
                    }

                    pipe.HIncrBy(seller, "funds", (long)price);
                    pipe.HIncrBy(buyer, "funds", (long)-price);
                    pipe.SAdd(inventory, itemid);
                    pipe.ZRem("market:", item);
                    pipe.EndPipe();
                    return true;
                }
                catch
                {
                    continue;
                }
            }

            return false;

        }

        /********************************************************************************************************
        # <start id="update-token"/>
        def update_token(conn, token, user, item=None):
            timestamp = time.time()                             #A
            conn.hset('login:', token, user)                    #B
            conn.zadd('recent:', {token: timestamp})            #C
            if item:
                conn.zadd('viewed:' + token, {item: timestamp}) #D
                conn.zremrangebyrank('viewed:' + token, 0, -26) #E
                conn.zincrby('viewed:', -1, item)               #F
        # <end id="update-token"/>
        *********************************************************************************************************/

        #region update-token
        public void update_token(CSRedisClient conn, string token, string user, string item = null)
        {
            var timestamp = (DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds;
            conn.HSet("login:", token, user);
            conn.ZAdd("recent:", ((decimal)timestamp, token));
            if (!string.IsNullOrEmpty(item))
            {
                conn.ZAdd("viewed:" + token, ((decimal)timestamp, token));
                conn.ZRemRangeByRank("viewed:" + token, 0, -26);
                conn.ZIncrBy("viewed:", item, -1);
            }
        }
        #endregion

        /********************************************************************************************************
        # <start id="update-token-pipeline"/>
        def update_token_pipeline(conn, token, user, item= None):
        timestamp = time.time()
        pipe = conn.pipeline(False)                         #A
        pipe.hset('login:', token, user)
        pipe.zadd('recent:', {token: timestamp
        })
        if item:
        pipe.zadd('viewed:' + token, {item: timestamp
        })
        pipe.zremrangebyrank('viewed:' + token, 0, -26)
        pipe.zincrby('viewed:', -1, item)
        pipe.execute()                                      #B
        # <end id="update-token-pipeline"/>
        *********************************************************************************************************/

        #region update-token-pipeline
        public void update_token_pipeline(CSRedisClient conn, string token, string user, string item = null)
        {
            var timestamp = (DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds;
            var pipe = conn.StartPipe();
            pipe.HSet("login:", token, user);
            pipe.ZAdd("recent:", ((decimal)timestamp, token));
            if (!string.IsNullOrEmpty(item))
            {
                pipe.ZAdd("viewed:" + token, ((decimal)timestamp, token));
                pipe.ZRemRangeByRank("viewed:" + token, 0, -26);
                pipe.ZIncrBy("viewed:", item, -1);
            }
            pipe.EndPipe();
        }
        #endregion

        /********************************************************************************************************
        # <start id="simple-pipeline-benchmark-code"/>
        def benchmark_update_token(conn, duration):
        for function in (update_token, update_token_pipeline):      #A
        count = 0                                               #B
        start = time.time()                                     #B
        end = start + duration                                  #B
        while time.time() < end:
           count += 1
           function(conn, 'token', 'user', 'item')             #C
        delta = time.time() - start                             #D
        print(function.__name__, count, delta, count / delta)    #E
        # <end id="simple-pipeline-benchmark-code"/>
        *********************************************************************************************************/

        #region simple-pipeline-benchmark-code
        public void simple_pipeline_benchmark_code(CSRedisClient conn, int duration)
        {
            foreach (var function in new Action<CSRedisClient, string, string, string>[] { update_token, update_token_pipeline })
            {
                int count = 0;
                var start = DateTime.Now;
                var end = start.AddSeconds(duration);
                while (DateTime.Now < end)
                {
                    count += 1;
                    function(conn, "token", "user", "item");
                }

                var delta = (DateTime.Now - start).TotalSeconds;
                Console.WriteLine($"{function.Method.Name}, {count}, {delta}, {count / delta}");
            }
        }
        #endregion

        /********************************************************************************************************
        def test_list_item(self):
        import pprint
        conn = self.conn

        print("We need to set up just enough state so that a user can list an item")
        seller = 'userX'
        item = 'itemX'
        conn.sadd('inventory:' + seller, item)
        i = conn.smembers('inventory:' + seller)
        print("The user's inventory has:", i)
        self.assertTrue(i)
        print()

        print("Listing the item...")
        l = list_item(conn, item, seller, 10)
        print("Listing the item succeeded?", l)
        self.assertTrue(l)
        r = conn.zrange('market:', 0, -1, withscores = True)
        print("The market contains:")
        pprint.pprint(r)
        self.assertTrue(r)
        self.assertTrue(any(x[0] == b'itemX.userX' for x in r))
        *********************************************************************************************************/

        public void test_list_item(CSRedisClient conn)
        {
            Console.WriteLine("We need to set up just enough state so that a user can list an item");
            var seller = "userX";
            var item = "itemX";
            conn.SAdd("inventory:" + seller, item);
            var i = conn.SMembers("inventory:" + seller);
            Console.WriteLine("The user's inventory has:{i}");
            Console.WriteLine();

            Console.WriteLine("Listing the item...");
            var l = list_item(conn, item, seller, 10);
            Console.WriteLine($"Listing the item succeeded?{l}");

            var r = conn.ZRangeWithScores("market:", 0, -1);
            Console.WriteLine($"The market contains:{PrintHelper.ValueTuple2String(r)}");

        }

        /********************************************************************************************************
        def test_purchase_item(self):
        self.test_list_item()
        conn = self.conn


        print("We need to set up just enough state so a user can buy an item")
        buyer = 'userY'
        conn.hset('users:userY', 'funds', 125)
        r = conn.hgetall('users:userY')
        print("The user has some money:", r)
        self.assertTrue(r)
        self.assertTrue(r.get(b'funds'))
        print()

        print("Let's purchase an item")
        p = purchase_item(conn, 'userY', 'itemX', 'userX', 10)
        print("Purchasing an item succeeded?", p)
        self.assertTrue(p)
        r = conn.hgetall('users:userY')
        print("Their money is now:", r)
        self.assertTrue(r)
        i = conn.smembers('inventory:' + buyer)
        print("Their inventory is now:", i)
        self.assertTrue(i)
        self.assertTrue(b'itemX' in i)
        self.assertEqual(conn.zscore('market:', 'itemX.userX'), None)
        *********************************************************************************************************/

        #region
        public void test_purchase_item(CSRedisClient conn)
        {
            Console.WriteLine("We need to set up just enough state so a user can buy an item");
            var buyer = "userY";
            conn.HSet("users:userY", "funds", 125);
            var r = conn.HGetAll("users:userY");
            Console.WriteLine($"The user has some money:{PrintHelper.Dictionary2String(r)}");
            Console.WriteLine();

            Console.WriteLine("Let's purchase an item");
            var p = purchase_item(conn, "userY", "itemX", "userX", 10);
            Console.WriteLine($"Purchasing an item succeeded?{p}");
            r = conn.HGetAll("users:userY");
            Console.WriteLine($"Their money is now:{PrintHelper.Dictionary2String(r)}");
            var i = conn.SMembers("inventory:" + buyer);
            Console.WriteLine("Their inventory is now:", PrintHelper.StringArray2String(i));
        }
        #endregion

        public void Test()
        {
            var conn = new CSRedis.CSRedisClient("127.0.0.1:6379,defaultDatabase=0,poolsize=500,ssl=false,writeBuffer=10240");

            simple_pipeline_benchmark_code(conn, 5);

            test_list_item(conn);

            test_purchase_item(conn);
        }
    }
}
